﻿// Spun AJAX Framework
// Copyright (C) 2008 Blake Hegerle
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Spun.Models;

namespace Spun.Naming.Paths
{
    /* Given a model and its binding context, this class will provide a list of all of
     * the loop-free binding paths that identify this object relative to a bound model. */
    public class PathEnumerator
    {
        public PathEnumerator(ModelBase Model, NamingContext Context)
        {
            this.Model = Model;
            this.Context = Context;

            this.PathList = new List<Path>();

            using (Context.LockingContext.ReadLock)
                GetPaths(Model, new Dictionary<ModelBase, bool>(), new List<ModelBase>());
        }

        public IEnumerable<string> PathStrings
        {
            get
            {
                return from Path in Paths
                       select Path.PathString;
            }
        }

        public IEnumerable<Path> Paths { get { return PathList; } }

        private void GetPaths(
            ModelBase Model,
            IDictionary<ModelBase, bool> Visited,
            IEnumerable<ModelBase> PathToThisModel)
        {
            if (Visited.ContainsKey(Model))
                return;

            Visited[Model] = true;

            foreach (var BindingName in Context.Names(Model))
                AddPath(Model, BindingName, PathToThisModel);

            PathToThisModel = Prepend(Model, PathToThisModel);

            foreach (var ReferencesToThisModel in Context.ModelContext.ReferencesTo(Model))
                GetPaths(ReferencesToThisModel, Visited, PathToThisModel);
        }

        private void AddPath(ModelBase Model, string BindingName, IEnumerable<ModelBase> PathToThisModel)
        {
            if (PathToThisModel.Count() > 0)
            {
                foreach (var Path in WalkPaths(Model, PathToThisModel.GetEnumerator()))
                {
                    var Node = new NamePathNode(BindingName);

                    PathList.Add(new Path(Prepend(Node, Path)));
                }
            }
            else
            {
                var Node = new NamePathNode(BindingName);

                PathList.Add(new Path(new IPathNode[] { Node }));
            }
        }

        private IEnumerable<IEnumerable<IPathNode>> WalkPaths(ModelBase Current, IEnumerator<ModelBase> enumerator)
        {
            if (enumerator.MoveNext())
            {
                ModelBase Next = enumerator.Current;

                var PropertyReferences = from CurrentMember in Current.AllMembersValues
                                         where CurrentMember.Value == Next
                                         select CurrentMember.Key;

                var PathsFromCurrent = new List<IEnumerable<IPathNode>>();

                foreach (var PathEnds in WalkPaths(Next, enumerator))
                {
                    foreach (var PropertyReference in PropertyReferences)
                    {
                        var PathFromCurrent = Prepend(new PropertyPathNode(PropertyReference), PathEnds);
                        PathsFromCurrent.Add(PathFromCurrent);
                    }
                }

                return PathsFromCurrent;
            }
            else
            {
                var End = new List<IEnumerable<IPathNode>>();
                End.Add(new List<IPathNode>());
                return End;
            }
        }

        private IEnumerable<T> Prepend<T>(T Element, IEnumerable<T> Enumerable)
        {
            var List = new List<T>();

            List.Add(Element);
            List.AddRange(Enumerable);

            return List;
        }

        private ModelBase Model;
        private NamingContext Context;
        private List<Path> PathList;
    }
}
